package com.sikaryofficial.auth.controller;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.nacos.shaded.com.google.common.collect.Maps;
import com.sikaryofficial.auth.config.HiSMKAuthConfig;
import com.sikaryofficial.auth.constant.AuthConstants;
import com.sikaryofficial.auth.form.LoginBody;
import com.sikaryofficial.auth.form.LoginH5Body;
import com.sikaryofficial.auth.form.RegisterBody;
import com.sikaryofficial.auth.form.RegisterH5Body;
import com.sikaryofficial.auth.form.ValidBody;
import com.sikaryofficial.auth.service.SysLoginService;
import com.sikaryofficial.common.core.constant.CacheConstants;
import com.sikaryofficial.common.core.constant.ChannelEnum;
import com.sikaryofficial.common.core.constant.DeviceIdEnum;
import com.sikaryofficial.common.core.constant.EvaluateTypeEnum;
import com.sikaryofficial.common.core.constant.RegisterTypeEnum;
import com.sikaryofficial.common.core.constant.SecurityConstants;
import com.sikaryofficial.common.core.constant.TokenConstants;
import com.sikaryofficial.common.core.domain.R;
import com.sikaryofficial.common.core.exception.ServiceException;
import com.sikaryofficial.common.core.utils.JwtUtils;
import com.sikaryofficial.common.core.utils.ServletUtils;
import com.sikaryofficial.common.core.utils.StringUtils;
import com.sikaryofficial.common.redis.service.RedisService;
import com.sikaryofficial.common.security.auth.AuthUtil;
import com.sikaryofficial.common.security.service.TokenService;
import com.sikaryofficial.common.security.utils.SecurityUtils;
import com.sikaryofficial.system.api.RemoteUserService;
import com.sikaryofficial.system.api.domain.SysUser;
import com.sikaryofficial.system.api.model.LoginUser;
import io.jsonwebtoken.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.util.Map;
import java.util.Objects;

/**
 * token 控制
 *
 * @author qinjinyuan
 */
@RestController
public class TokenController {
    private static final Logger log = LoggerFactory.getLogger(TokenController.class);
    @Autowired
    private TokenService tokenService;
    @Autowired
    private SysLoginService sysLoginService;
    @Resource
    private HiSMKAuthConfig authConfig;
    @Autowired
    private RemoteUserService remoteUserService;
    @Autowired
    private RedisService redisService;
    @Value("${hismk.auth.ske.appSecret}")
    private String appSecret;

    @PostMapping("login")
    public R<?> login(@RequestBody LoginBody form) {
        String channel = ServletUtils.getRequest().getHeader(SecurityConstants.CHANNEL_KEY);
        String deviceId = ServletUtils.getRequest().getHeader(SecurityConstants.DEVICE_KEY);
        if (Objects.isNull(DeviceIdEnum.getNameByCode(deviceId))) {
            log.error("deviceId value is error.");
            throw new ServiceException("deviceId value is error.");
        }
        if (Objects.isNull(ChannelEnum.getNameByCode(channel))) {
            log.error("channel value is error.");
            throw new ServiceException("channel value is error.");
        }
        // 用户登录
        LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
        // 删除token
        tokenService.delLoginUserCache(userInfo.getSysUser().getUserId(), channel, deviceId);
        // 获取登录token
        return R.ok(tokenService.createToken(userInfo, channel, deviceId));
    }

    @DeleteMapping("logout")
    public R<?> logout(HttpServletRequest request) {
        String token = SecurityUtils.getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            String username = JwtUtils.getUserName(token);
            // 删除用户缓存记录
            AuthUtil.logoutByToken(token);
            // 记录用户退出日志
            sysLoginService.logout(username);
        }
        return R.ok();
    }

    @PostMapping("refresh")
    public R<?> refresh(HttpServletRequest request) {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser)) {
            // 由于refresh 接口不走 HeaderInterceptor拦截器，需要直接从header中获取channel和deviceId
            String channel = ServletUtils.getRequest().getHeader(SecurityConstants.CHANNEL_KEY);
            String deviceId = ServletUtils.getRequest().getHeader(SecurityConstants.DEVICE_KEY);
            // 刷新令牌有效期
            tokenService.refreshToken(loginUser, channel, deviceId);
            return R.ok();
        }
        log.error("LoginUser is empty, Please login");
        throw new ServiceException("token is invalid");
    }

    @PostMapping("hasLogin")
    public R<Map> hasLogin(HttpServletRequest request) {
        String token = getToken(SecurityUtils.getToken(request));
        if (StringUtils.isEmpty(token)) {
            log.info("token is empty");
            Map<String, Object> result = Maps.newHashMap();
            result.put("hasLogin", false);
            return R.ok(result);
        }
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            throw new ServiceException("Token has expired or verification is incorrect");
        }
        String userKey = JwtUtils.getUserKey(claims);
        boolean isLogin = redisService.hasKey(getTokenKey(userKey));
        if (!isLogin) {
            throw new ServiceException("Login status has expired");
        }
        String userid = JwtUtils.getUserId(claims);
        /**
         * 缓存读取年龄
         */
        // 查看年龄是否满足限制
        String catchKey = MessageFormat.format(CacheConstants.USER_AGE_KEY, userid);
        Integer age = redisService.getCacheObject(catchKey);
        Map<String, Object> result = Maps.newHashMap();
        result.put("age", age);
        result.put("hasLogin", true);
        return R.ok(result);
    }

    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    private String getToken(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }

    @PostMapping("register")
    public R<?> register(@RequestBody RegisterBody registerBody) {
        // 用户注册
        sysLoginService.register(registerBody.getUsername(), registerBody.getPassword());
        return R.ok();
    }

    /**
     * 定制登录接口
     *
     * @param form
     * @return
     */
    @PostMapping("h5Login")
    public R<?> h5Login(@RequestBody LoginH5Body form) {
        if (StringUtils.isAnyBlank(form.getEmailAddress())) {
            throw new ServiceException("username is empty.");
        }
        // 前端不传登录用户名: username; 后端根据邮箱把用户名获取。
        String username = sysLoginService.getUserNameByEmail(form.getEmailAddress());
        if (StringUtils.isBlank(username)) {
            String brand = ServletUtils.getRequest().getHeader(SecurityConstants.BRAND_KEY);
            log.error("user is not exist.Email:{},brand:{}", form.getEmailAddress(), brand);
            throw new ServiceException("user is not exist.");
        }
        return R.ok(sysLoginService.loginOnly(username));
    }

    /**
     * 定制注册接口
     * <p>
     * 1、先注册，再登录
     * 2、 返回相关数据
     *
     * @param registerBody
     * @return
     */
    @PostMapping("h5Register")
    public R<Map<String, Object>> h5Register(@Validated @RequestBody RegisterH5Body registerBody) {
        // 注册登录公共方法
        registerBody.setBrandNo(ServletUtils.getRequest().getHeader(SecurityConstants.BRAND_KEY));
        // 先判断邮箱是否存在
        // 前端不传登录用户名: username; 后端根据邮箱把用户名获取。
        String username = sysLoginService.getUserNameByEmail(registerBody.getEmailAddress());
        Map<String, Object> result;
        if (StringUtils.isBlank(username)) {
            // 设置注册类型
            buildRegisterType(registerBody);
            // 注册并登录
            result = sysLoginService.registerAndLogin(registerBody);
            result.put(AuthConstants.HAS_REGISTER_KEY, true);
        } else {
            // 登录
            registerBody.setPassword(authConfig.getDefaultH5Pwd());
            result = sysLoginService.loginOnlyForH5(username);
            result.put(AuthConstants.HAS_REGISTER_KEY, false);
        }
        return R.ok(result);
    }

    private void buildRegisterType(RegisterH5Body registerBody) {
        if (CharSequenceUtil.isNotBlank(registerBody.getEvaluateType())
                && EvaluateTypeEnum.SCAN_CODE.getEvaluateType().equals(registerBody.getEvaluateType())) {
            registerBody.setRegisterType(RegisterTypeEnum.SCAN_CODE.getCode());
        } else {
            registerBody.setRegisterType(RegisterTypeEnum.NORMAL.getCode());
        }
    }

    /**
     * 邮箱校验
     *
     * @param req
     * @return
     */
    @PostMapping("checkEmailUnique")
    public R<Boolean> checkEmailUnique(@Validated @RequestBody ValidBody req) {
        return remoteUserService.checkEmailUnique(req.getEmailAddress());
    }

    /**
     * 外部注册接口
     *
     * @param registerBody
     * @return
     */
    @PostMapping("externalRegister")
    public R<Map<String, Object>> externalRegister(@Validated @RequestBody RegisterH5Body registerBody) {
        // 签名验证
        signValidate(registerBody);
        // 设置默认年龄
        registerBody.setAge(AuthConstants.DEFAULT_USER_AGE);
        registerBody.setIsoCountryCode("GBR");
        registerBody.setBrandNo(ServletUtils.getRequest().getHeader(SecurityConstants.BRAND_KEY));
        registerBody.setRegisterType(RegisterTypeEnum.SKE_OFFICIAL.getCode());
        // 注册登录公共方法
        Map<String, Object> result = sysLoginService.registerAndLogin(registerBody);
        return R.ok(result);
    }

    private void signValidate(RegisterH5Body registerBody) {
        // 签名验证
        if (Objects.isNull(registerBody.getSign()) || Objects.isNull(registerBody.getTimestamp())) {
            throw new ServiceException("sign is null or timestamp is null.");
        }
        // get 请求的签名，直接把值进行拼接，生成MD5
        String curSign = DigestUtil.md5Hex(registerBody.getEmailAddress() + registerBody.getTimestamp() + appSecret);
        boolean isValidSign = curSign.equals(registerBody.getSign());
        // 签名无效
        if (Boolean.FALSE.equals(isValidSign)) {
            log.info("curSign:{},inputSign:{}", curSign, registerBody.getSign());
            throw new ServiceException("sign is invalid.");
        }
    }

    /**
     * 更新用户昵称缓存
     *
     * @param userId
     * @param nickName
     */
    private void updateNickNameCache(Long userId, String nickName) {
        if (Objects.isNull(userId) || StringUtils.isBlank(nickName)) {
            return;
        }
        // 设置年龄缓存
        String catchKey = CacheConstants.USER_AVATAR_URL;
        SysUser sysUser = redisService.getCacheMapValue(catchKey, userId.toString());
        if (Objects.isNull(sysUser)) {
            sysUser = new SysUser(userId);
        }
        sysUser.setNickName(nickName);
        redisService.setCacheMapValue(catchKey, userId.toString(), sysUser);
    }
}
